package io.edap.protobuf.util;

import io.edap.protobuf.builder.JavaBuilder;
import io.edap.protobuf.wire.Field;
import io.edap.protobuf.wire.Option;
import io.edap.protobuf.wire.Proto;
import io.edap.protobuf.wire.ProtoEnum;

import java.util.Locale;
import java.util.Map;

public final class FieldUtils {
    private static final String HAS_PREFIX = "has";
    private static final String GETTER_PREFIX = "get";
    private static final String SETTER_PREFIX = "set";
    private static final String IS_PREFIX = "is";
    private static final String LIST = "java.util.List";
    private static final String ITERABLE = "java.lang.Iterable";
    private static final String GETTER_REPEATED_SUFFIX = "List";
    private static final String MAP_SUFFIX = "Map";
    private static final String PUT_PREFIX = "put";
    private static final String VALUE = "Value";
    private static final String EMPTY_STRING = "";

    private FieldUtils() {
        throw new IllegalAccessError("Utility class");
    }


    public static String getFieldHasName(Field field) {
        return HAS_PREFIX + Formatter.toPascalCase(field.getName());
    }

    /**
     * Returns map field getter name.
     */
    public static String getMapGetterName(Field field) {
        return GETTER_PREFIX + Formatter.toPascalCase(field.getName()) + MAP_SUFFIX;
    }

    /**
     * Returns a java field getter name for proto repeated field.
     */
    public static String getRepeatedFieldGetterName(Field field) {
        if (field.isRepeated()) {
            return GETTER_PREFIX + Formatter.toPascalCase(field.getName()) + GETTER_REPEATED_SUFFIX;
        }
        throw new IllegalArgumentException(field.toString());
    }

    /**
     * Returns a java field getter name for proto field.
     */
    public static String getFieldGetterName(Field field) {
        String getterName = GETTER_PREFIX + Formatter.toPascalCase(field.getName());
        if ("getClass".equals(getterName)) {
            return getterName + "_";
        }
        return getterName;
    }

    public static Field.Type getFieldType(Field field, Proto proto, Map<String, ProtoEnum> protoEnums) {
        String type = field.getTypeString();
        if (JavaBuilder.isBaseType(field)) {
            return Field.Type.valueOf(type.toUpperCase(Locale.ENGLISH));
        } else {
            String name = getJavaPackage(proto) + "." + type;
            if (protoEnums.containsKey(name)) {
                return Field.Type.ENUM;
            } else {
                return Field.Type.MESSAGE;
            }
        }
    }

    public static String getJavaPackage(Proto proto) {
        if (proto.getOptions() == null || proto.getOptions().isEmpty()) {
            return EMPTY_STRING;
        }
        for (Option option : proto.getOptions()) {
            if ("java_package".equalsIgnoreCase(option.getName())) {
                return option.getValue();
            }
        }
        return EMPTY_STRING;
    }

    /**
     * Returns a java enum field value getter name for proto field.
     */
    public static String getEnumFieldValueGetterName(Field field) {
        return GETTER_PREFIX + Formatter.toPascalCase(field.getName()) + VALUE;
    }

    private static String getFieldName(Field field) {
        return field.getName();
    }

    /**
     * Generate part of toString method for a single field.
     */
    public static String toStringPart(Field field, Proto proto, Map<String, ProtoEnum> protoEnums) {
        String getterName;
        if (field.isMap()) {
            getterName = getMapGetterName(field);
        } else if (field.isRepeated()) {
            getterName = getRepeatedFieldGetterName(field);
        } else {
            getterName = getFieldGetterName(field);
        }

        if (getFieldType(field, proto, protoEnums) == Field.Type.ENUM && !field.isRepeated()) {
            return "\"" + getFieldName(field) + "=\" + " + getterName + "() + '(' + " + getEnumFieldValueGetterName(field) + "() + ')'";
        }
        return "\"" + getFieldName(field) + "=\" + " + getterName + "()";
    }

    /**
     * Check if field type is numeric.
     */
    public static boolean isNumericType(Field field, Proto proto, Map<String, ProtoEnum> protoEnums) {
        Field.Type type = getFieldType(field, proto, protoEnums);
        switch (type) {
            case INT32:
            case INT64:
            case UINT32:
            case UINT64:
            case SINT32:
            case SINT64:
            case FIXED32:
            case FIXED64:
            case SFIXED32:
            case SFIXED64:
            case FLOAT:
            case DOUBLE:
                return true;
        }
        return false;
    }

    public static boolean isBooleanType(Field field, Proto proto, Map<String, ProtoEnum> protoEnums) {
        Field.Type type = getFieldType(field, proto, protoEnums);
        return Field.Type.BOOL == type;
    }
}
